﻿/*
 * User: RSny
 * Date: 5/15/2012
 * Time: 9:20 AM
 */
using System;
using System.Runtime.InteropServices;

namespace Aspects.Custom.Audio
{
    public class WaveIn
    {
        public WaveIn()
        {
            MyWaveDelegate = new WaveNative.WaveDelegate(CallBack);
        }
        
        private void CallBack(IntPtr waveInHandle, WaveMessage message, int userData, ref WaveHeader waveHeader, IntPtr reserved)
        {
            if (message == WaveMessage.WIM_DATA)
            {
                GCHandle hBuffer = (GCHandle)waveHeader.dwUser;
                WaveInBuffer buffer = (WaveInBuffer)hBuffer.Target;
                Exception exception = null;
                if (DataAvailable != null)
                {
                    DataAvailable(buffer.Data, buffer.BytesRecorded);
                }
                if (MaxSoundLevel != null)
                {
                    byte[] waveStream = new byte[buffer.BytesRecorded];
                    Marshal.Copy(buffer.Data, waveStream, 0, buffer.BytesRecorded);
                    MaxSoundLevel(GetMaxSound(GetWaveChannels(waveStream)));
                }
                if (recording)
                {
                    try
                    {
                        buffer.Reuse();
                    }
                    catch (Exception e)
                    {
                        recording = false;
                        exception = e;
                    }
                }
            }
        }
        
        public void StartRecording()
        {
            OpenWaveInDevice();
            if (recording)
                throw new InvalidOperationException("Already Recording");
            WaveHelper.Try(WaveNative.waveInStart(WaveInHandle));
            recording = true;
        }
        
        public void StopRecording()
        {
            recording = false;
            WaveHelper.Try(WaveNative.waveInStop(WaveInHandle));
        }
        
        private void OpenWaveInDevice()
        {
            CloseWaveInDevice();
            WaveHelper.Try(WaveNative.waveInOpen(out WaveInHandle, DeviceNumber, Format, MyWaveDelegate, 0 , WaveNative.CALLBACK_FUNCTION));
            CreateBuffers();
        }
        
        private void CloseWaveInDevice()
        {
            WaveNative.waveInReset(WaveInHandle);
            if (buffers != null)
            {
                for (int i = 0; i < buffers.Length; i++)
                {
                    buffers[i].Dispose();
                }
                buffers = null;
            }
            WaveNative.waveInClose(WaveInHandle);
            WaveInHandle = IntPtr.Zero;
        }
        
        private void CreateBuffers()
        {
            int bufferSize = BufferMilleseconds * Format.nAvgBytesPerSec / 1000;
            buffers = new WaveInBuffer[NumberOfBuffers];
            for (int i = 0; i < buffers.Length; i++)
            {
                buffers[i] = new WaveInBuffer(WaveInHandle, bufferSize);
            }
        }
        
        private short[] GetWaveChannels(byte[] waveStream)
        {
            short[] monoWave = new short[waveStream.Length/2];
            int h=0;
            for (int i = 0 ; i < waveStream.Length; i += 2)
            {
                monoWave[h] = BitConverter.ToInt16(waveStream, i);
                h++;
            }
            return monoWave;
        }
        
        private int GetMaxSound(short[] wave)
        {
            int maxSound = 0;
            for (int i = 0; i < wave.Length; i++)
            {
                maxSound = Math.Max(maxSound, Math.Abs((int)wave[i]));
            }
            return maxSound;
        }
        
        public static int DeviceCount { get{ return WaveNative.waveInGetNumDevs();}}
        public int DeviceNumber { get { return _deviceNumber; } set { _deviceNumber = value;}}
        
        private bool recording = false;
        private int _deviceNumber = 0;
        public WaveFormat Format= new WaveFormat(8000, 16,1);
        private int BufferMilleseconds = 100;
        private int NumberOfBuffers = 3;
        private IntPtr WaveInHandle;
        private WaveInBuffer[] buffers;
        private WaveNative.WaveDelegate MyWaveDelegate;
        
        public event DataAvailableHandler DataAvailable;
        /// <summary>
        /// Fires during recording and returns highest peak between left and right channels
        /// </summary>
        public event MaxSoundHandler MaxSoundLevel;
        
        public delegate void DataAvailableHandler(IntPtr data, int size);
        public delegate void MaxSoundHandler(int MaxSound);
    }
}